// Copyright (c)  YISH. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Text;
using NSQL.DateFormats;
using NSQL.Expressions;
using NSQL.Expressions.Functions;

namespace NSQL.Generator
{
    public class SQLiteGenerator : BaseGenerator, IExpressionVisitor<StringBuilder>
    {
        public static SqlGeneratorSettings DefaultSettings => new SqlGeneratorSettings()
        {
            DatetimeFormatter = SQLiteDateFormatter.Instance
        };
        public static readonly ISqlGenerator Instance = new SQLiteGenerator(DefaultSettings);

        private readonly SQLiteKeyWords _;

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IStdDevFunction stdDevFunction)
        {
            throw new NotSupportedException();
        }
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IPercentileFunction expression)
        {
            throw new NotSupportedException();
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IDiffFunction expression)
        {
            var builder = new StringBuilder();
            if (expression.Type != ExtractType.Day)
            {
                builder.Append('(');
            }

            builder.Append(_.Julian).Append(_.Day).Append('(').Append(expression.End.Accept(this)).Append(')')
                .Append(" - ")
                .Append(_.Julian).Append(_.Day).Append('(').Append(expression.Start.Accept(this)).Append(')');

            if (expression.Type != ExtractType.Day)
            {
                builder.Append(')');
            }

            switch (expression.Type)
            {
                case ExtractType.Year:
                    builder.Append(" / ").Append(365);
                    break;
                case ExtractType.Month:
                    builder.Append(" / ").Append(30);
                    break;
                case ExtractType.Week:
                    builder.Append(" / ").Append(7);
                    break;
                case ExtractType.Day:
                    break;
                case ExtractType.Hour:
                    builder.Append(" * ").Append(24);
                    break;
                case ExtractType.Minute:
                    builder.Append(" * ").Append(24 * 60);
                    break;
                case ExtractType.Second:
                    builder.Append(" * ").Append(24 * 60 * 60);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            return builder;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IAddIntervalFunction addIntervalFunction)
        {
            var builder = new StringBuilder();
            builder.Append(_.Datetime);
            builder.Append('(');
            
            builder.Append(addIntervalFunction.DateTime.Accept(this));
            builder.Append(", ");


            builder.Append('\'');

            if (addIntervalFunction.Interval.Interval > 0)
            {
                builder.Append('+');
            }
            
            builder.Append(addIntervalFunction.Interval.Interval).Append(' ');
            
            switch (addIntervalFunction.Interval.Type)
            {
                case IntervalType.Years:
                    builder.Append(_.Year);
                    break;
                case IntervalType.Months:
                    builder.Append(_.Month);
                    break;
                case IntervalType.Days:
                    builder.Append(_.Day);
                    break;
                case IntervalType.Hours:
                    builder.Append(_.Hour);
                    break;
                case IntervalType.Minutes:
                    builder.Append(_.Minute);
                    break;
                case IntervalType.Seconds:
                    builder.Append(_.Second);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            builder.Append('\'').Append(')');
            return builder;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IMedianFunction expression)
        {
            var builder = new StringBuilder();
            builder.Append(_.Median);
            builder.Append('(').Append(expression.Expr.Accept(this)).Append(')');
            return builder;
        }
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ILeftFunction leftFunction)
        {
            return new SubStringFunction(leftFunction.Text, (IntegerExpression) 1, leftFunction.Length).Accept(this);
        }
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IRightFunction rightFunction)
        {
            return new SubStringFunction(rightFunction.Text, new LengthFunction(rightFunction.Text) - rightFunction.Length + 1, rightFunction.Length).Accept(this);
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IExtractFunction extractFunction)
        {
            throw new NotSupportedException();
        }

        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ILogFunction expression)
        {
            throw new NotSupportedException();
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(INvlFunction nvlFunction)
        {
            var builder = new StringBuilder();
            var hasThen = !(nvlFunction.Then is null);

            if (hasThen)
            {
                builder.Append(_.If);
                builder.Append('(');
                builder.Append(nvlFunction.Expr.Accept(this));
                builder.Append(' ').Append(_.Is);
                builder.Append(' ').Append(_.Null);
                builder.Append(", ").Append(nvlFunction.Default.Accept(this));
                builder.Append(", ").Append(nvlFunction.Then.Accept(this));
                builder.Append(')');
            }
            else
            {
                builder.Append(_.If).Append(_.Null);
                builder.Append('(');
                builder.Append(nvlFunction.Expr.Accept(this));
                builder.Append(", ").Append(nvlFunction.Default.Accept(this));
                builder.Append(')');
            }
            return builder;
        }
        
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(IFormatFunction expression)
        {
            var builder = new StringBuilder();

            if (expression.IsDateFormat)
            {
                builder.Append(_.Strftime);
                builder.Append('(');
                builder.Append(expression.Items[0].Accept(this));
                builder.Append(", ");
                var f = (StringExpression) Settings.DatetimeFormatter.Format(expression.Format.Value);
                builder.Append(f.Accept(this));
                builder.Append(')');
            }
            else
            {
                throw new NotSupportedException();
            }
            return builder;
        }

        protected override StringBuilder FormatPaging(StringBuilder statement, int skip, int limit)
        {
            if (skip <= 0 && limit <= 0) return statement;
            
            statement.Append(' ');
            statement.Append(_.Limit);
            statement.Append(' ');
            if (limit > 0)
            {
                statement.Append(limit);
            }
            else
            {
                statement.Append(-1);
            }

            if (skip <= 0) return statement;
            
            statement.Append(' ');
            statement.Append(_.Offset);
            statement.Append(' ').Append(skip);

            return statement;
        }
        
        StringBuilder IFunctionVisitor<StringBuilder>.Visit(ICastFunction castFunction)
        {
            var targetType = castFunction.TargetType;
            var builder = new StringBuilder();
            if (string.Equals(targetType, "timestamp", StringComparison.OrdinalIgnoreCase) ||
                string.Equals(targetType, "datetime", StringComparison.OrdinalIgnoreCase))
            {
                builder.Append(_.FormatLetter("Datetime"))
                    .Append('(')
                    .Append(castFunction.Expr.Accept(this))
                    .Append(')');
                return builder;
            }
            if (string.Equals(targetType, "time", StringComparison.OrdinalIgnoreCase))
            {
                builder.Append(_.FormatLetter("Time"))
                    .Append('(')
                    .Append(castFunction.Expr.Accept(this))
                    .Append(')');
                return builder;
            }
            if (string.Equals(targetType, "date", StringComparison.OrdinalIgnoreCase))
            {
                builder.Append(_.FormatLetter("Date"))
                    .Append('(')
                    .Append(castFunction.Expr.Accept(this))
                    .Append(')');
                return builder;
            }
            
            builder.Append(_.Cast);
            builder.Append('(');
            builder.Append(castFunction.Expr.Accept(this));
            builder.Append(' ').Append(_.As).Append(' ');
            builder.Append(targetType);
            builder.Append(')');
            return builder;
        }
        public SQLiteGenerator(Func<SqlGeneratorSettings, SqlGeneratorSettings> setupAction)
            : this(setupAction(DefaultSettings))
        {
        }
        public SQLiteGenerator(SqlGeneratorSettings settings) : this(settings, new SQLiteKeyWords(settings.LetterCase))
        {
        }
        private SQLiteGenerator(SqlGeneratorSettings settings, SQLiteKeyWords keyWords) : base(settings, keyWords)
        {
            _ = keyWords;
        }
        
        protected class SQLiteKeyWords: KeyWords
        {
            public SQLiteKeyWords(LetterCase letterCase) : base(letterCase)
            {
            }
            
            public override string Now => FormatLetter("Current_Timestamp");
            public string Offset => FormatLetter(nameof(Offset));
            public string Datetime => FormatLetter(nameof(Datetime));
            public string Julian => FormatLetter(nameof(Julian));
            public override string TrimStart => FormatLetter("LTrim");
            public override string TrimEnd => FormatLetter("RTrim");
            public string If => FormatLetter(nameof(If));
            public string Strftime => FormatLetter(nameof(Strftime));

        }
    }
}